﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace huqiang.FileTool
{
    public class VirtualStream : System.IO.Stream
    {
        Stream realStream;
        VirtualFileInfo virtualFile = new VirtualFileInfo();
        List<VirtualFileInfo> fileList = new List<VirtualFileInfo>();
        public static VirtualStream CreateFromVirtualStream(VirtualStream other, string[] keys)
        {
            VirtualStream vs = new VirtualStream();
            int l = 0;
            for (int i = 0; i < keys.Length; i++)
            {
                VirtualFileInfo vfi = other.Find(keys[i]);
                if (vfi != null)
                {
                    VirtualFileInfo vdi = vs.AddVirtualFile(keys[i]);
                    vdi.allLen = vfi.allLen;
                    vdi.position = l;
                    l += vdi.allLen;
                    vdi.End = l;
                    vdi.name = vfi.name;
                    vdi.dataOffset= vfi.dataOffset;
                    vdi.name = vfi.name;
                    vdi.key = vfi.key;
                    vs.fileList.Add(vdi);
                }
                else
                {
                }
            }
            vs.len = l;
            vs.realStream = other.realStream;
            return vs;
        }
        public static VirtualStream LoadFromFile(Stream stream)
        {
            VirtualStream virtualStream = new VirtualStream();
            virtualStream.realStream = stream;
            virtualStream.Load();
            return virtualStream;
        }
        private void Load()
        {
            virtualFile.subset = new List<VirtualFileInfo>();
            byte[] buf = new byte[256];
            int offset = 0;
            while (true)
            {
                realStream.Read(buf, 0, 4);
                int nl = buf.ReadInt32(0);
                realStream.Read(buf, 4, 4);
                int dl = buf.ReadInt32(4);
                realStream.Read(buf, 0, nl);
                string key = Encoding.UTF8.GetString(buf, 0, nl);
                var vfi = AddVirtualFile(key);
                //vfi.key = key;
                vfi.keyLen = nl;
                vfi.dataLen = dl;
                vfi.allLen = nl + dl + 8;
                vfi.position = offset;
                vfi.dataOffset = offset;
                vfi.End = vfi.position + vfi.allLen;
                offset +=vfi.allLen;
                fileList.Add(vfi);
                if (offset >= realStream.Length)
                    break;
                realStream.Seek(offset, SeekOrigin.Begin);
            }
        }
        private VirtualFileInfo AddVirtualFile(string key)
        {
            string[] tree = key.Split('/');
            int c = tree.Length - 1;
            VirtualFileInfo vfi = virtualFile;

            for (int i = 0; i < tree.Length; i++)
            {
                VirtualFileInfo t = null;
                if (vfi.subset == null)
                    vfi.subset = new List<VirtualFileInfo>();
                else t = vfi.subset.Find((o) => { return o.name == tree[i]; });
                if (t == null)
                {
                    t = new VirtualFileInfo();
                    t.name = tree[i];
                    //t.subset = new List<VirtualFileInfo>();
                    vfi.subset.Add(t);
                }
                vfi = t;
            }
            vfi.key = key;
            return vfi;
        }
        public VirtualFileInfo Find(string key)
        {
            if (fileList == null)
                return null;
            return fileList.Find((o) => { return o.key == key; });
        }
        public override bool CanRead => true;

        public override bool CanSeek => true;

        public override bool CanWrite => false;
        long len;
        public override long Length => len;
        long pos;
        public override long Position {
            get { 
                return pos; 
            }
            set
            {
                if (value > len)
                    value = len;
                pos = value;
            }
        }
        
        private VirtualStream()
        {
        }
        public override void Flush()
        {

        }
        public byte[] ReadFile(string key)
        {
            if (realStream != null)
            {
                VirtualFileInfo vfi = Find(key);
                if (vfi != null)
                {
                    byte[] dat = new byte[vfi.dataLen];
                    realStream.Seek(vfi.dataOffset + vfi.keyLen + 8, SeekOrigin.Begin);
                    realStream.Read(dat, 0, vfi.dataLen);
                    return dat;
                }
            }
            return null;
        }
        public override int Read(byte[] buffer, int offset, int count)
        {
            if (pos >= len)
                return 0;
            int readCount = 0;
            for (int i = 0; i < fileList.Count; i++)
            {
                var vf = fileList[i];
                if (pos < vf.End)
                {
                    int os = (int)pos - vf.position;
                    int max = vf.allLen - os;
                    if (max > count)
                        max = count;
                    realStream.Seek(vf.dataOffset + os, SeekOrigin.Begin);
                    realStream.Read(buffer, offset, max);
                    readCount += max;
                    count -= max;
                    pos += max;
                    if (pos >= len)
                        break;
                    if (count <= 0)
                        break;
                    offset += max;
                }
            }
            return readCount;
        }

        public override long Seek(long offset, SeekOrigin origin)
        {
            pos = offset;
            return 0;
        }

        public override void SetLength(long value)
        {
        }

        public override void Write(byte[] buffer, int offset, int count)
        {
        }
        public override void Close()
        {
            base.Close();
            if (realStream != null)
                realStream.Close();
        }
        protected override void Dispose(bool disposing)
        {
            base.Dispose(disposing);
            if (realStream != null)
            {
                realStream.Dispose();
                realStream = null;
            }
        }
    }
}
